/*
 *  Copyright 2001-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package ar.uba.fi.posgrado.common.dao;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import ar.uba.fi.posgrado.common.exception.MissingArgumentException;
import ar.uba.fi.posgrado.common.model.AbstractDomainObject;

/**
 * Clase abstracta que maneja las operaciones basicas de persistencia.<br>
 * Extiende {@link HibernateDaoSupport hibernate dao support} e implementa
 * {@link GenericDAO generic DAO}.<br>
 * Provee la operatoria basica de manejo de acceso a datos y delega a sus
 * extensiones metodos especificos lo q hace q esta clase implemente Template
 * Mehotd [GoF]({@link http://en.wikipedia.org/wiki/Template_method_pattern T.Method}).<br>
 * 
 * La transaccionabilidad es manejada por fuera de esta clase y no es
 * responsabilidad de los hijos manejarlo.<br>
 * 
 * 
 * @author nicolas.gonzalez
 * @see HibernateDaoSupport
 * @see GenericDAO
 */
public abstract class AbstractDAO<T> extends HibernateDaoSupport implements
		GenericDAO<T> {

	/** Logger */
	private static Logger logger = Logger.getLogger(AbstractDAO.class);

	/**
	 * Metodo que hace que esta clase sea un Template Method [GoF]
	 * 
	 * @return Devuelve un Class de la clase especifica que representa el DAO
	 */
	@SuppressWarnings("unchecked")
	protected abstract Class getClazz();

	/**
	 * @see ar.uba.fi.posgrado.common.dao.GenericDAO#findById(java.lang.Long)
	 */
	@SuppressWarnings("unchecked")
	public AbstractDomainObject findById(Long id)
			throws MissingArgumentException, NoSuchEntityException,
			DAOException {
		if (id == null) {
			throw new MissingArgumentException("El id no puede ser nulo");
		}
		AbstractDomainObject response = null;
		Class clazz = this.getClazz();
		if (logger.isDebugEnabled()) {
			logger.debug("Buscando una instancia de " + clazz + ", con id: "
					+ id);
		}

		try {
			response = (AbstractDomainObject) this.getHibernateTemplate().load(
					clazz, id);
		} catch (ObjectRetrievalFailureException e) {
			throw new NoSuchEntityException("No existe intancia de " + clazz
					+ ", con id: " + id + " " + e.getMessage(), e);
		} catch (DataAccessException e) {
			throw new DAOException("Excepcion al buscar una intancia de "
					+ clazz + ", con id: " + id + ". Mensaje: "
					+ e.getMessage(), e);
		}

		return response;
	}

	/**
	 * @see ar.uba.fi.posgrado.common.dao.GenericDAO#remove(ar.uba.fi.posgrado.common.model.AbstractDomainObject)
	 */
	@SuppressWarnings("unchecked")
	public void remove(AbstractDomainObject object)
			throws MissingArgumentException, DAOException, ConstraintViolationException {
		if (object == null) {
			throw new MissingArgumentException(
					"El objeto a eliminar no puede ser nulo");
		}
		Class clazz = this.getClazz();
		if (logger.isDebugEnabled()) {
			logger.debug("Eliminando una instancia de " + clazz
					+ ". Instancia: " + object);
		}

		try {
			this.getHibernateTemplate().delete(object);
		} catch (DataAccessException e) {
			throw new DAOException("Excepcion al eliminar una intancia de "
					+ clazz + ". Instancia : " + object + ". Mensaje: "
					+ e.getMessage(), e);
		}
	}

	/**
	 * @see ar.uba.fi.posgrado.common.dao.GenericDAO#save(ar.uba.fi.posgrado.common.model.AbstractDomainObject)
	 */
	@SuppressWarnings("unchecked")
	public Long save(AbstractDomainObject object)
			throws MissingArgumentException, DAOException {
		if (object == null) {
			throw new MissingArgumentException(
					"El objeto a guardar no puede ser nulo");
		}
		Class clazz = this.getClazz();
		if (logger.isDebugEnabled()) {
			logger.debug("Guardando una instancia de " + clazz
					+ ". Instancia: " + object);
		}

		try {
			return (Long) this.getHibernateTemplate().save(object);
		} catch (DataAccessException e) {
			throw new DAOException("Excepcion al guardar una intancia de "
					+ clazz + ". Instancia : " + object + ". Mensaje: "
					+ e.getMessage(), e);
		}
	}

	/**
	 * @see ar.uba.fi.posgrado.common.dao.GenericDAO#update(ar.uba.fi.posgrado.common.model.AbstractDomainObject)
	 */
	@SuppressWarnings("unchecked")
	public void update(AbstractDomainObject object)
			throws MissingArgumentException, DAOException {
		if (object == null) {
			throw new MissingArgumentException(
					"El objeto a modificar no puede ser nulo");
		}
		Class clazz = this.getClazz();
		if (logger.isDebugEnabled()) {
			logger.debug("Modificando una instancia de " + clazz
					+ ". Instancia: " + object);
		}

		try {
			this.getHibernateTemplate().update(object);
		} catch (DataAccessException e) {
			throw new DAOException("Excepcion al modificar una intancia de "
					+ clazz + ". Instancia : " + object + ". Mensaje: "
					+ e.getMessage(), e);
		}
	}

	/**
	 * @see ar.uba.fi.posgrado.common.dao.GenericDAO#update(ar.uba.fi.posgrado.common.model.AbstractDomainObject)
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll() throws DAOException {
		List response = null;
		Class clazz = this.getClazz();
		if (logger.isDebugEnabled()) {
			logger.debug("Buscando todas las instancias de " + clazz);
		}

		try {
			response = this.getHibernateTemplate().loadAll(clazz);
		} catch (DataAccessException e) {
			throw new DAOException(
					"Excepcion al buscar todas las intancias de " + clazz
							+ ". Mensaje: " + e.getMessage(), e);
		}
		return response;
	}

	/**
	 * @see ar.uba.fi.posgrado.common.dao.GenericDAO#findByCriterions(java.util.List)
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByCriterions(final List<Criterion> criterions)
			throws DAOException {
		List response = null;
		final Class clazz = this.getClazz();
		if (logger.isDebugEnabled()) {
			logger.debug("Buscando instancias de " + clazz + " con criterios");
		}

		try {
			response = (List) this.getHibernateTemplate().execute(
					new HibernateCallback() {
						public Object doInHibernate(Session session)
								throws HibernateException, SQLException {
							Criteria criteria = session.createCriteria(clazz);
							for (Criterion crit : criterions) {
								criteria.add(crit);
							}
							return criteria.list();
						}
					});
			if (response == null) {
				response = new ArrayList<T>();
			}
		} catch (DataAccessException e) {
			throw new DAOException("Excepcion al buscar instancias de " + clazz
					+ " con criterios. Mensaje: " + e.getMessage(), e);
		}

		return response;

	}

	/**
	 * @see ar.uba.fi.posgrado.common.dao.GenericDAO#generateRemovedSufix()
	 */
	public String generateRemovedSufix() {
		return "_REMOVED_";
	}

}
